import { isFunction, isObject, cloneDeep } from 'lodash-es';
import { guidGenerator } from '@/utils/guid';
import type {
  RequestInterceptors,
  TransformRequestData,
  RequestInterceptor,
  RequestInterceptorCatcher,
  ResponseInterceptor,
  ResponseInterceptorCatcher,
  TransformResponseData,
  DefaultOptions,
  RequestUrl,
  CancelFunction,
  MultipleGuid,
  RequestObject,
  RequestInterface
} from './types';

export default class Request implements RequestInterface {
  static requestMap = new Map<RequestUrl, RequestObject>();
  private transformRequestData: TransformRequestData = async (options) =>
    options;

  private requestInterceptor: RequestInterceptor = async (options) => options;
  private requestInterceptorCatcher: RequestInterceptorCatcher | undefined;
  private responseInterceptor: ResponseInterceptor | undefined;
  private responseInterceptorCatcher: ResponseInterceptorCatcher | undefined;
  private transformResponseData: TransformResponseData | undefined;

  constructor (
    instanceOptions: RequestInterceptors,
    private defaultOptions: DefaultOptions
  ) {
    this.setupInterceptors(instanceOptions);
  }

  createRequest (
    url: RequestUrl,
    cancelFunction: CancelFunction,
    multipleGuid?: MultipleGuid
  ) {
    // 是否多任务
    if (multipleGuid) {
      const object = Request.requestMap.get(url);
      if (object) {
        const obj = object as Record<MultipleGuid, CancelFunction>;
        Reflect.set(obj, multipleGuid, cancelFunction);
      } else {
        Request.requestMap.set(url, { multipleGuid: cancelFunction });
      }
    } else {
      Request.requestMap.set(url, cancelFunction);
    }
  }

  removeRequest (url: RequestUrl, multipleGuid?: MultipleGuid) {
    const requestItem = Request.requestMap.get(url);
    if (!requestItem) return;
    if (isFunction(requestItem)) {
      Request.requestMap.delete(url);
    } else if (isObject(requestItem)) {
      if (multipleGuid) {
        Reflect.deleteProperty(requestItem, multipleGuid);
      } else {
        Request.requestMap.delete(url);
      }
    }
  }

  abortRequest (url: RequestUrl, multipleGuid?: MultipleGuid) {
    const requestItem = Request.requestMap.get(url);
    if (!requestItem) return;
    if (isFunction(requestItem)) {
      requestItem();
      Request.requestMap.delete(url);
    } else if (isObject(requestItem)) {
      if (multipleGuid) {
        const cancelFunction = requestItem[multipleGuid];
        cancelFunction?.();
        Reflect.deleteProperty(requestItem, multipleGuid);
      } else {
        for (const key in requestItem) {
          const cancelFunction = requestItem[key];
          cancelFunction?.();
        }
        Request.requestMap.delete(url);
      }
    }
  }

  setupInterceptors (instanceOptions: RequestInterceptors) {
    const {
      transformRequestData,
      requestInterceptor,
      requestInterceptorCatcher,
      responseInterceptor,
      responseInterceptorCatcher,
      transformResponseData
    } = instanceOptions || {};
    if (isFunction(transformRequestData)) {
      this.transformRequestData = transformRequestData;
    }
    if (isFunction(requestInterceptor)) {
      this.requestInterceptor = requestInterceptor;
    }
    if (isFunction(requestInterceptorCatcher)) {
      this.requestInterceptorCatcher = requestInterceptorCatcher;
    }
    if (isFunction(responseInterceptor)) {
      this.responseInterceptor = responseInterceptor;
    }
    if (isFunction(responseInterceptorCatcher)) {
      this.responseInterceptorCatcher = responseInterceptorCatcher;
    }
    if (isFunction(transformResponseData)) {
      this.transformResponseData = transformResponseData;
    }
  }

  async request<T extends AnyResult> (
    options: RequestOptions
  ): Promise<Result<T>> {
    // 请求前处理数据
    let opt: RequestOptions = options;
    try {
      const interceptedOptions = await this.requestInterceptor(options);
      if (!interceptedOptions) {
        return { __error: '请求终止', __requestOptions: options };
      }
      opt = interceptedOptions;
      const transformedOptions = await this.transformRequestData(opt);
      if (!transformedOptions) {
        return { __error: '请求终止', __requestOptions: opt };
      }
      opt = transformedOptions;
    } catch (error) {
      if (isFunction(this.requestInterceptorCatcher)) {
        return await this.requestInterceptorCatcher(opt, error as Error);
      } else {
        const errorResponse = {
          __error: '请求错误',
          __errMsg: error
        } as RequestErrorResponseResult;
        return errorResponse;
      }
    }
    // 默认值覆盖
    if (opt.baseUrl === undefined) {
      Reflect.set(opt, 'baseUrl', this.defaultOptions.baseUrl);
    }
    if (opt.timeout === undefined) {
      Reflect.set(opt, 'timeout', this.defaultOptions.timeout);
    }
    // 接口路径
    const url = opt.baseUrl + opt.url;
    // 如果允许重复请求产生唯一标识
    const multipleGuid = options.multiple ? guidGenerator(false) : undefined;
    // 如果不允许重复请求把该接口所有的请求都清除
    if (!multipleGuid) this.abortRequest(opt.url);
    // 创建请求
    const requestPromise = new Promise<Result<T>>((resolve, reject) => {
      // 是否已经终止
      let aborted = false;
      // 记录请求时间
      const __requestTime = new Date().getTime();
      // 请求成功
      type SuccesResult = UniApp.RequestSuccessCallbackResult;
      const requestSuccess = async (response: SuccesResult) => {
        if (aborted) return;
        try {
          // 将 RequestSuccessCallbackResult 转为 Result 类型
          let data = response.data as T as Record<string, unknown>;
          const __responseTime = new Date().getTime();
          if (typeof response.data !== 'object') {
            console.error(`响应错误:${url}接口返回不是JSON对象`);
            console.log(response);
            data = {} as T as Record<string, unknown>;
          }
          let result = {
            ...data,
            __requestOptions: opt,
            __requestTime,
            __responseTime
          } as Result<T>;
          if (isFunction(this.responseInterceptor)) {
            result = await this.responseInterceptor(opt, result, response);
            if (result?.__error) {
              const res = { ...result, __requestTime, __responseTime };
              return resolve(res);
            }
          }
          if (isFunction(this.transformResponseData)) {
            result = await this.transformResponseData(opt, result, response);
            if (result?.__error) {
              return resolve({ ...result, __requestTime, __responseTime });
            }
          }
          resolve(result);
        } catch (error) {
          if (isFunction(this.responseInterceptorCatcher)) {
            const errorResult = await this.responseInterceptorCatcher(
              opt,
              error as Error
            );
            const __responseTime = new Date().getTime();
            const res = { ...errorResult, __requestTime, __responseTime };
            resolve(res);
          } else {
            reject(error);
          }
        }
      };
      // 请求失败
      type FailError = UniApp.GeneralCallbackResult &
        Partial<UniApp.RequestSuccessCallbackResult>;
      const requestFail = async (error: FailError) => {
        if (aborted) return;
        // 兼容非20x状态码都走fail的情况
        if (error.statusCode !== undefined) {
          const response = error as SuccesResult;
          return requestSuccess(response);
        }
        if (isFunction(this.responseInterceptorCatcher)) {
          const errorResult = await this.responseInterceptorCatcher(
            opt,
            new Error(error.errMsg)
          );
          const __responseTime = new Date().getTime();
          const res = { ...errorResult, __requestTime, __responseTime };
          resolve(res);
        } else {
          reject(error);
        }
      };
      // 处理 patch 请求
      if (opt.method === 'PATCH') {
        if (opt.headers) {
          opt.headers['X-HTTP-Method-Override'] = 'PATCH';
        } else {
          Reflect.set(opt, 'headers', { 'X-HTTP-Method-Override': 'PATCH' });
        }
      }
      // 请求实例
      const requestTask = uni.request({
        url,
        data: opt.data,
        header: opt.headers,
        timeout: opt.timeout,
        method: opt.method === 'PATCH' ? 'POST' : opt.method,
        dataType: opt.dataType,
        // #ifndef MP-ALIPAY
        responseType: opt.responseType,
        // #endif
        success: requestSuccess,
        fail: requestFail,
        complete: (response) => {
          if (aborted) return;
          const time = new Date().toLocaleTimeString();
          console.log(opt.apiInfo, time, { request: opt, response });
          this.removeRequest(opt.url, multipleGuid);
        }
      });
      // 注册终止函数
      if (isFunction(requestTask?.abort)) {
        const cancelFunction = () => {
          requestTask?.abort();
          aborted = true;
          const time = new Date().toLocaleTimeString();
          console.warn('请求被终止', opt.apiInfo, time, { request: opt });
          const __responseTime = new Date().getTime();
          resolve({
            __error: '请求终止',
            __requestOptions: opt,
            __requestTime,
            __responseTime
          });
        };
        this.createRequest(opt.url, cancelFunction, multipleGuid);
      } else {
        const cancelFunction = () => {
          aborted = true;
          const time = new Date().toLocaleTimeString();
          console.warn('请求被终止', opt.apiInfo, time, { request: opt });
          const __responseTime = new Date().getTime();
          resolve({
            __error: '请求终止',
            __requestOptions: opt,
            __requestTime,
            __responseTime
          });
        };
        this.createRequest(opt.url, cancelFunction, multipleGuid);
      }
    });
    const response = await requestPromise;
    return response;
  }

  get<T extends AnyResult> (options: RequestOptions) {
    const opt = cloneDeep(options);
    Reflect.set(opt, 'method', 'GET');
    return this.request<T>(opt);
  }

  post<T extends AnyResult> (options: RequestOptions) {
    const opt = cloneDeep(options);
    Reflect.set(opt, 'method', 'POST');
    return this.request<T>(opt);
  }

  patch<T extends AnyResult> (options: RequestOptions) {
    const opt = cloneDeep(options);
    Reflect.set(opt, 'method', 'PATCH');
    return this.request<T>(opt);
  }

  delete<T extends AnyResult> (options: RequestOptions) {
    const opt = cloneDeep(options);
    Reflect.set(opt, 'method', 'DELETE');
    return this.request<T>(opt);
  }

  put<T extends AnyResult> (options: RequestOptions) {
    const opt = cloneDeep(options);
    Reflect.set(opt, 'method', 'PUT');
    return this.request<T>(opt);
  }

  cancelAllRequest () {
    for (const key of Request.requestMap.keys()) {
      this.abortRequest(key);
    }
  }
}
